﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Windows.Controls;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;

namespace FxArchitect.ContentPipeline
{
    public class ContentProject : ILogger
    {
        #region Static Data

        const string CAT_XNA_FRAMEWORK = "XNA Framework Content Pipeline",
                     XNA_TARGETS = "$(MSBuildExtensionsPath)\\Microsoft\\XNA Game Studio\\v4.0\\Microsoft.Xna.GameStudio.ContentPipeline.targets";
        static string[] ms_pipelineAssemblies =
        {
            "Microsoft.Xna.Framework.Content.Pipeline",
            "Microsoft.Xna.Framework.Content.Pipeline.EffectImporter",
            "Microsoft.Xna.Framework.Content.Pipeline.FBXImporter",
            "Microsoft.Xna.Framework.Content.Pipeline.TextureImporter",
            "Microsoft.Xna.Framework.Content.Pipeline.XImporter",
            "Microsoft.Xna.Framework.Content.Pipeline.VideoImporters",
            "Microsoft.Xna.Framework.Content.Pipeline.AudioImporters"
        };
        static ContentExtensionCollection ms_standardExtensions;
        static ContentProject()
        {
            ms_standardExtensions = new ContentExtensionCollection();
            for (int i = 0; i < ms_pipelineAssemblies.Length; i++)
            {
                ms_standardExtensions.Add(new ContentExtensionProject(ms_pipelineAssemblies[i]));
            }
        }

        /// <summary>
        /// Should be set to the selected content project's extension list.
        /// </summary>
        internal static ContentExtensionCollection ContentExtensions;

        #endregion

        #region Fields

        string m_name, m_projectFolder, m_projectFile, m_rootDirectory, m_contentRootOutput;
        XnaProfile m_profile;

        Project m_project;
        ProjectRootElement m_projectRootElement;
        BuildParameters m_buildParameters;
        List<ProjectItem> m_projectItems, m_referenceItems;
        List<ContentFile> m_contentItems;
        ContentExtensionCollection m_extensions;
        TreeViewItem m_rootNode, m_refNode, m_outputNode;
        List<string> m_outputFiles;

        #endregion

        #region Properties

        [Category("Compiler Settings")]
        [DisplayName("XNA Profile")]
        [Description("Determines which run-time profile to compile against.")]
        [TypeConverter(typeof(XnaProfileConverter))]
        public XnaProfile XnaProfile { get { return m_profile; } set { m_profile = value; } }

        [DisplayName("Project File")]
        [Description("Name of the content project file.")]
        public string ProjectFile { get { return m_projectFile; } set { m_projectFile = value; } }

        [DisplayName("Project Folder")]
        [Description("Full path to the folder containing the content source files.")]
        [ReadOnly(true)]
        public string ProjectFolder { get { return m_projectFolder; } }

        [Category(CAT_XNA_FRAMEWORK)]
        [DisplayName("Content Root Directory")]
        [Description("Path to place the content files relative to the game folder.")]
        public string RootDirectory { get { return m_rootDirectory; } set { m_rootDirectory = value; } }

        /// <summary>
        /// Gets the tree node for this project.
        /// </summary>
        [Browsable(false)]
        public TreeViewItem TreeNodes { get { return m_rootNode; } }
        /// <summary>
        /// Gets the file extensions supported based on which importers are available.
        /// </summary>
        [Browsable(false)]
        public string ContentExtensionsFilter
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                foreach (ContentExtensionProject extension in m_extensions)
                {
                    foreach (ContentImporterInfo info in extension.Importers)
                    {
                        string ext = string.Empty;
                        foreach (string str in info.FileExtensions)
                            ext += string.Format("*{0};", str);

                        if (ext.Length == 0)
                            ext = "*.*;";
                        ext = ext.Substring(0, ext.Length - 1);

                        sb.AppendFormat("{0} ({1})|{1}|", info.DisplayName, ext);
                    }
                }
                sb.Append("All files (*.*)|*.*");
                return sb.ToString();
            }
        }
        /// <summary>
        /// Full path to each of the files that were built from this project.
        /// </summary>
        [Browsable(false)]
        public List<string> OutputFiles { get { return m_outputFiles; } }
        /// <summary>
        /// Full path to the location the content manager can accept as a root directory safely.
        /// </summary>
        [Browsable(false)]
        public string ContentRootOutput { get { return m_contentRootOutput; } }

        #endregion

        public ContentProject(string name, string projectFolder)
        {
            m_name = name;
            m_projectFolder = projectFolder;
            m_projectFile = name + ".contentproj";
            m_rootDirectory = "";

            #region Windows Integration

            ContextMenu rootContextMenu = new ContextMenu();

            MenuItem addItem = new MenuItem();
            addItem.Header = "Add Item";

            MenuItem addNewItem = new MenuItem();
            addNewItem.Header = "Add New Item";
            addNewItem.Click += new System.Windows.RoutedEventHandler(addNewItem_Click);
            addItem.Items.Add(addNewItem);

            MenuItem addExistItem = new MenuItem();
            addExistItem.Header = "Add Existing Item";
            addExistItem.Click += new System.Windows.RoutedEventHandler(addExistItem_Click);
            addItem.Items.Add(addExistItem);

            MenuItem buildItem = new MenuItem();
            buildItem.Header = "Build";
            buildItem.Click += new System.Windows.RoutedEventHandler(buildItem_Click);
            
            rootContextMenu.Items.Add(addItem);
            rootContextMenu.Items.Add(buildItem);

            ContextMenu refContextMenu = new ContextMenu();

            MenuItem addRefItem = new MenuItem();
            addRefItem.Header = "Add Reference";
            addRefItem.Click += new System.Windows.RoutedEventHandler(addRefItem_Click);

            refContextMenu.Items.Add(addRefItem);

            m_rootNode = new TreeViewItem();
            m_rootNode.Header = m_name;
            m_rootNode.Tag = this;
            m_rootNode.ContextMenu = rootContextMenu;

            m_refNode = new TreeViewItem();
            m_refNode.Header = "References";
            m_refNode.ContextMenu = refContextMenu;
            m_rootNode.Items.Add(m_refNode);

            m_outputNode = new TreeViewItem();
            m_outputNode.Header = "Compiled Content";
            m_rootNode.Items.Add(m_outputNode);

            #endregion

            m_projectItems = new List<ProjectItem>();
            m_referenceItems = new List<ProjectItem>();
            m_contentItems = new List<ContentFile>();
            m_extensions = new ContentExtensionCollection();
            m_outputFiles = new List<string>();

            for (int i = 0; i < ms_standardExtensions.Count; i++)
                AddExtension(ms_standardExtensions[i]);
        }

        #region Context Menu Response

        void addRefItem_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            Select();

            Microsoft.Win32.OpenFileDialog fileDialog = new Microsoft.Win32.OpenFileDialog();
            fileDialog.Filter = "Library files (*.dll)|*.dll|C# project files (*.csproj)|*.csproj";
            if (fileDialog.ShowDialog() ?? false)
                AddExtension(fileDialog.FileName);
        }

        void buildItem_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            Select();
            Build();

            m_outputNode.Items.Clear();
            for (int i = 0; i < m_outputFiles.Count; i++)
            {
                TreeViewItem newNode = new TreeViewItem();
                newNode.Header = m_outputFiles[i].Replace(m_contentRootOutput + "\\", string.Empty);
                newNode.Tag = m_contentRootOutput;
                m_outputNode.Items.Add(newNode);
            }
        }

        void addNewItem_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            System.Windows.MessageBox.Show("I'm sorry but this feature is not yet implimented.");
        }

        void addExistItem_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            Select();

            Microsoft.Win32.OpenFileDialog fileDialog = new Microsoft.Win32.OpenFileDialog();
            fileDialog.Filter = ContentExtensionsFilter;
            if (fileDialog.ShowDialog() ?? false)
                AddContentFile(fileDialog.FileName);
        }

        #endregion

        /// <summary>
        /// Select this project so that property grid importers and processors reflect the 
        /// correct content project references.
        /// </summary>
        public void Select()
        {
            ContentExtensions = m_extensions;
        }

        #region Listing

        /// <summary>
        /// Adds a new content extension to this project.
        /// </summary>
        /// <param name="filename"></param>
        public void AddExtension(string filename)
        {
            AddExtension(new ContentExtensionProject(filename));
        }
        public void AddExtension(ContentExtensionProject extension)
        {
            m_extensions.Add(extension);
            m_refNode.Items.Add(extension.CreateTreeNode());
        }
        public void RemoveExtension(ContentExtensionProject extension)
        {
            m_extensions.Remove(extension);
            foreach (TreeViewItem node in m_refNode.Items)
            {
                if (node.Tag == extension)
                {
                    m_refNode.Items.Remove(node);
                    break;
                }
            }
        }

        public ContentFile AddContentFile(string filename)
        {
            ContentFile cf = new ContentFile(filename);
            m_contentItems.Add(cf);

            FileInfo fi = new FileInfo(filename);

            ContentImporterInfo importer = m_extensions.FindImporterByFileExtension(fi.Extension);
            ContentProcessorInfo processor = null;
            if (importer != null)
            {
                processor = m_extensions.FindProcessorByTypeName(importer.DefaultProcessor);
                if (processor == null)
                    processor = m_extensions.FindProcessorByDisplayName(importer.DisplayName);
            }

            cf.Importer = importer;
            cf.Processor = processor;

            m_rootNode.Items.Add(cf.TreeNodes);

            return cf;
        }
        public void RemoveContentFile(ContentFile cf)
        {
            m_rootNode.Items.Remove(cf.TreeNodes);
            m_contentItems.Remove(cf);
        }

        #endregion

        #region Build

        void CreateBuildProject()
        {
            string projectPath = Path.Combine(m_projectFolder, m_projectFile);
            string outputPath = Path.Combine(m_projectFolder, "bin");

            m_projectRootElement = ProjectRootElement.Create(projectPath);
            m_projectRootElement.AddImport(XNA_TARGETS);

            m_project = new Project(m_projectRootElement);
            m_project.SetProperty("XnaPlatform", "Windows");
            m_project.SetProperty("XnaProfile", Enum.GetName(typeof(XnaProfile), m_profile));
            m_project.SetProperty("XnaFrameworkVersion", "v4.0");
            m_project.SetProperty("Configuration", "Release");
            m_project.SetProperty("OutputPath", "output");

            m_buildParameters = new BuildParameters(ProjectCollection.GlobalProjectCollection);
            m_buildParameters.Loggers = new ILogger[] { this };
        }

        void UpdateProject()
        {
            if (m_project == null)
                CreateBuildProject();

            // remove the existing items
            m_project.RemoveItems(m_projectItems);
            m_projectItems.Clear();

            // remove the existing extensions
            m_project.RemoveItems(m_referenceItems);
            m_referenceItems.Clear();

            m_outputFiles.Clear();

            // add all of the extension references
            foreach (ContentExtensionProject extension in m_extensions)
            {
                ProjectItem item = extension.WriteReference(m_project);
                if (item != null)
                    m_referenceItems.Add(item);
            }

            // add all of the content files
            foreach (ContentFile content in m_contentItems)
            {
                ProjectItem item = content.WriteItem(m_project);
                m_projectItems.Add(item);
            }
        }

        public string Build()
        {
            UpdateProject();
            m_project.Save();
            m_errors.Clear();

            BuildManager.DefaultBuildManager.BeginBuild(m_buildParameters);
            BuildRequestData request = new BuildRequestData(m_project.CreateProjectInstance(), new string[0]);
            BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(request);

            submission.ExecuteAsync(null, null);
            submission.WaitHandle.WaitOne();

            BuildManager.DefaultBuildManager.EndBuild();
            
            //string outputPath = m_project.GetPropertyValue("OutputPath");
            //string buildPath = Path.Combine(m_project.DirectoryPath, outputPath);
            //m_contentRootOutput = Path.Combine(buildPath, m_name);

            //StreamReader sr = File.OpenText(Path.Combine(m_project.DirectoryPath, "cachefile--targetpath.txt"));
            //try
            //{
            //    string line;
            //    while ((line = sr.ReadLine()) != null)
            //    {
            //        if (!string.IsNullOrWhiteSpace(line))
            //            m_outputFiles.Add(Path.Combine(buildPath, line));
            //    }
            //}
            //finally
            //{
            //    if (sr != null)
            //    {
            //        sr.Close();
            //        sr.Dispose();
            //    }
            //}

            return (submission.BuildResult.OverallResult == BuildResultCode.Failure) ?
                    string.Join("\n", m_errors.ToArray()) :
                    string.Empty;
        }

        #endregion

        #region ILogger Members

        List<string> m_errors = new List<string>();

        [Browsable(false)]
        public List<string> Errors { get { return m_errors; } }

        string ILogger.Parameters { get; set; }
        LoggerVerbosity ILogger.Verbosity { get { return LoggerVerbosity.Normal; } set { } }

        public event AnyEventHandler BuildEventRaised = delegate { };

        void ILogger.Initialize(IEventSource eventSource)
        {
            if (eventSource != null)
            {
                eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
                //eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
                //eventSource.AnyEventRaised += new AnyEventHandler(eventSource_AnyEventRaised);
            }
        }

        void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
        {
            //throw new NotImplementedException();
            //BuildEventRaised(sender, e);
        }

        void eventSource_AnyEventRaised(object sender, BuildEventArgs e)
        {
            //BuildEventRaised(sender, e);
        }

        void ILogger.Shutdown() { }

        void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
        {
            m_errors.Add(e.Message);
        }

        #endregion
    }
}
